package com.tencent.sr.iris.activity.service.event.activity;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.tencent.sr.iris.activity.common.util.ShardingIdUtil;
import com.tencent.sr.iris.activity.dependency.user.UserCoreDependency;
import com.tencent.sr.iris.activity.domain.event.dto.ActivityCheckResultDTO;
import com.tencent.sr.iris.activity.domain.event.dto.ActivityDTO;
import com.tencent.sr.iris.activity.domain.event.dto.ActivityTaskDTO;
import com.tencent.sr.iris.activity.interfaces.enums.ActivityEventTypeEnum;
import com.tencent.sr.iris.activity.interfaces.event.dto.ActivityEventDTO;
import com.tencent.sr.iris.activity.repository.entity.TIrisActivityTaskDO;
import com.tencent.sr.iris.activity.repository.entity.TIrisUserTaskRecordDO;
import com.tencent.sr.iris.activity.repository.mapper.TIrisUserTaskRecordMapper;
import com.tencent.sr.iris.activity.repository.mapper.example.TIrisUserTaskRecordExample;
import com.tencent.sr.iris.activity.repository.singlemapper.TIrisActivityTaskMapper;
import com.tencent.sr.iris.activity.repository.singlemapper.example.TIrisActivityTaskExample;
import com.tencent.sr.iris.activity.service.activity.ActivityCommonService;
import com.tencent.sr.iris.activity.service.activity.IrisUserTaskRecordService;
import com.tencent.sr.iris.activity.service.config.ActivityConfig;
import com.tencent.sr.iris.activity.service.constant.ActivityRedisKey;
import com.tencent.sr.iris.activity.service.event.task.IrisTaskExecutor;
import com.tencent.sr.iris.activity.service.event.util.ActivityCommonUtilService;
import com.tencent.sr.iris.activity.service.util.BeanConvertUtil;
import com.tencent.sr.iris.user.core.service.interfaces.enums.StaffFromTypeEnum;
import com.tencent.sr.iris.user.core.service.interfaces.enums.TutorChannelSettingEnum;
import com.tencent.sr.iris.user.core.service.interfaces.enums.UserLevelEnum;
import com.tencent.sr.iris.user.core.service.interfaces.response.user.IrisQueryUsersInTagsResponse;
import com.tencent.sr.rmall.common.exception.retail.TRetailErrorException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneOffset;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author xiaojun
 *         注：当前活动处理器的对象名称是 活动类型ActivityEventTypeEnum的code
 */
@Slf4j
@Component
public class IrisInviteGiftActivityExecutor extends AbstractIrisActivityExecutor {

    @Resource
    public ActivityConfig activityConfig;

    @Resource
    private TIrisUserTaskRecordMapper userTaskRecordMapper;

    @Resource
    private TIrisActivityTaskMapper activityTaskMapper;
    @Resource
    private ActivityCommonService activityCommonService;
    @Resource
    private ActivityCommonUtilService activityCommonUtilService;
    @Resource
    private IrisUserTaskRecordService userTaskRecordService;

    @Resource
    private UserCoreDependency userCoreDependency;

    @Resource
    private RedissonClient redissonClient;

    @Override
    protected void process(ActivityEventDTO msg, ActivityDTO activity, List<String> eventHandingInfoList) {
        RLock lock = null;
        try {
            lock = tryLock(msg, eventHandingInfoList);

            //1.活动准入规则校验
            ActivityCheckResultDTO activityFlag = doActivityCheck(activity, msg);
            if (!activityFlag.isSatisfy()) {
                log.info("不满足活动准入规则,activityId={},errorMsg:{}, msg={}", activity.getBizId(), activityFlag.getErrorMsg(),
                        JSONObject.toJSONString(msg));
                eventHandingInfoList.add("不满足活动准入规则,errorMsg=" + activityFlag.getErrorMsg());
                return;
            }

            ActivityCheckResultDTO doAccessConditionCheckResult = doAccessConditionCheck(activity, msg);
            if (!doAccessConditionCheckResult.isSatisfy()) {
                log.info("不满足活动准入规则,activityId={},errorMsg:{}, msg={}", activity.getBizId(),
                        doAccessConditionCheckResult.getErrorMsg(),
                        JSONObject.toJSONString(msg));
                eventHandingInfoList.add("不满足活动准入规则,errorMsg=" + doAccessConditionCheckResult.getErrorMsg());
                //如果不满足活动任务门槛，删除已存在的进行中任务记录
                int i = userTaskRecordService.deleteInProgressTaskRecord(msg.getUid(), activity.getBizId(),
                        null);
                //有记录清除邀请上限缓存
                userTaskRecordService.deleteInviteLimitCache(msg.getPid(), msg.getUid());
                activityRecordService.handleInProgressActivityRecord(msg.getUid(), activity.getBizId());
                eventHandingInfoList.add("不满足活动准入规则，删除进行中的任务记录,result:" + i + "条");
                return;
            }

            //查询用户进度记录,已有记录的还是走原来的任务
            isEverParticipated(msg.getUid(), activity);

            activity.getActivityTaskDTOS().forEach(t -> {
                //获取任务执行器
                IrisTaskExecutor taskExecutor = getTaskExecutor(msg, activity, t);
                if (Objects.nonNull(taskExecutor)) {
                    taskExecutor.execute(msg, activity, t, null);
                } else {
                    log.error("任务处理器找寻失败 任务ID:{}", t.getBizId());
                    eventHandingInfoList.add("任务处理器找寻失败，taskID=" + t.getBizId());
                }
            });

            activityRecordService.addOrUpdateActivityRecord(msg.getUid(), msg.getEventTime(), activity.getBizId(),
                    activity.getPeriodType(), activity.getPeriod());
        } catch (Exception e) {
            log.error("邀请有礼活动流程错误：", e);
            throw new TRetailErrorException(e);
        } finally {
            if (lock != null && lock.isLocked() && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    /**
     * 加锁
     * @param msg
     * @param eventHandingInfoList
     * @return
     * @throws InterruptedException
     */
    private RLock tryLock(ActivityEventDTO msg, List<String> eventHandingInfoList) throws InterruptedException {
        RLock lock;
        String lockKey = ActivityRedisKey.getInviteTaskNewLockKey(msg.getPid());
        lock = redissonClient.getLock(lockKey);
        if (!lock.tryLock(30, TimeUnit.SECONDS)) {
            eventHandingInfoList.add("邀请有礼活动处理：获取分布式锁失败，当前上级邀请任务正在处理");
            log.info("邀请有礼活动处理：获取分布式锁失败，当前上级邀请任务正在处理,pid:{},uid:{},msgId:{}",
                    msg.getPid(), msg.getUid(), msg.getIdempotent());
            //抛个异常重试
            throw new TRetailErrorException("邀请有礼活动处理获取分布式锁失败");
        }
        return lock;
    }

    //已经参与过邀请有礼活动,就接着执行已参与的任务进度
    private void isEverParticipated(String uid, ActivityDTO activity) {

        TIrisUserTaskRecordExample example = new TIrisUserTaskRecordExample();
        example.createCriteria().andPropertyEqualTo(TIrisUserTaskRecordExample.UID, uid)
                .andPropertyEqualTo(TIrisUserTaskRecordExample.ACTIVITY_ID, activity.getBizId())
                //记录是删除状态也要读取，db做了唯一校验，如果现在又符合条件了，更新的时候会把任务状态改成进行中
//                .andPropertyEqualTo(TIrisUserTaskRecordExample.STATUS, UserTaskRecordStatusEnum.NORMAL.getCode())
                .andPropertyEqualTo(TIrisUserTaskRecordExample.SHARDING_ID, ShardingIdUtil.sharding(uid));
        //查询该用户是否已经有正常状态的邀请有礼进度记录
        List<TIrisUserTaskRecordDO> irisUserTaskRecordDOS = userTaskRecordMapper.selectByExample(example);

        if (CollectionUtils.isNotEmpty(irisUserTaskRecordDOS)) {
            List<String> taskIds = irisUserTaskRecordDOS.stream().map(TIrisUserTaskRecordDO::getTaskId)
                    .collect(Collectors.toList());
            TIrisActivityTaskExample taskExample = new TIrisActivityTaskExample();
            taskExample.createCriteria().andPropertyIn(TIrisActivityTaskExample.BIZ_ID, taskIds);
            //查询出该用户已经参与的邀请有礼活动,接着执行用户已经参与的邀请有礼任务
            List<TIrisActivityTaskDO> activityTaskDOS = activityTaskMapper.selectByExample(taskExample);
            activity.setActivityTaskDTOS(BeanConvertUtil.INSTANCE.convertActivityTaskDO2DTOList(activityTaskDOS));
        }
    }

    @Override
    protected void after(ActivityEventDTO eventDTO, ActivityDTO act) {

    }

    @Override
    protected List<ActivityTaskDTO> getTask(ActivityEventDTO eventDTO, ActivityDTO act) {
        return null;
    }


    @Override
    protected void check(ActivityEventDTO msg, ActivityDTO activity) {

    }

    @Override
    public ActivityCheckResultDTO doActivityCheck(ActivityDTO activity, ActivityEventDTO msg) {
        log.info("开始校验事件基本信息邀请有礼活动,ActivityEventDTO:{}", JSON.toJSONString(msg));

        //判断成为见习会员时间是否在活动有效期内
        Long becomeTraineeTime = activityCommonService.queryTraineeTime(msg.getUid());
        if (becomeTraineeTime == null) {
            log.info("用户成为见习会员的时间为空");
            return ActivityCheckResultDTO.builder().satisfy(false).errorMsg("用户成为见习会员的时间为空").build();
        }
        if (BooleanUtils.isFalse(activityCommonUtilService.checkActivityTime(activity, becomeTraineeTime))) {
            log.info("活动有效期校验不通过");
            return ActivityCheckResultDTO.builder().satisfy(false).errorMsg("超出活动时间范围").build();
        }
        //事件时间是否是成为见习会员30天内
        if (BooleanUtils.isFalse(activityCommonUtilService.checkTraineeTime(msg, becomeTraineeTime))) {
            log.info("见习会员30天内校验不通过");
            return ActivityCheckResultDTO.builder().satisfy(false).errorMsg("不满足注册见习会员30内时间条件或者非活动开始后成为见习会员,无法参与邀请有礼活动")
                    .build();
        }

        return ActivityCheckResultDTO.builder().satisfy(true).build();
    }

    /**
     * 活动准入条件判断，不符合条件会删除用户在活动下的进行中的任务记录
     *
     * @param activity
     * @param msg
     * @return
     */
    @Override
    public ActivityCheckResultDTO doAccessConditionCheck(ActivityDTO activity, ActivityEventDTO msg) {

        String uid = msg.getUid();
        String pid = msg.getPid();

        boolean becomeTraineeEvent = ActivityEventTypeEnum.BECOME_TRAINEE.getCode().equals(msg.getEventType());
        //判断是否在活动有效期内
        Long becomeTraineeTime = queryTraineeTime(uid, becomeTraineeEvent);
        if (becomeTraineeTime == null) {
            log.info("用户成为见习会员的时间为空");
            return ActivityCheckResultDTO.builder().satisfy(false).errorMsg("用户成为见习会员的时间为空").build();
        }

        IrisQueryUsersInTagsResponse userTags = getUserTags(uid, becomeTraineeEvent, becomeTraineeTime);
        List<String> tagIdList = Optional.ofNullable(userTags.getTagIdList()).orElse(Collections.emptyList());
        if (!CollectionUtil.containsAny(tagIdList, Arrays.asList(TutorChannelSettingEnum.GROUP.getValue(),
                TutorChannelSettingEnum.COMMON_TUTOR.getValue()))) {
            log.info("上级【{}】的下级【{}】不符合用户升级场景值：团长、公共导师", pid, uid);
            return ActivityCheckResultDTO.builder().satisfy(false).errorMsg("下级不符合用户升级场景值：团长、公共导师").build();
        }

        //下级用户等级范围定义
        List<String> uidUserLevels;
        LocalDate becomeTraineeDate = Instant.ofEpochMilli(becomeTraineeTime).atZone(ZoneOffset.ofHours(+8))
                .toLocalDate();

        //严格口径生效时间，默认5.1号
        if (becomeTraineeDate.isBefore(getStrictStandardStartDate(uid))) {
            //是否是非特邀会员
            if (BooleanUtils.isFalse(activityCommonUtilService.checkSpecialInvite(msg.getUid()))) {
                log.info("特邀会员校验不通过");
                return ActivityCheckResultDTO.builder().satisfy(false).errorMsg("用户是特邀会员,无法参与邀请有礼活动").build();
            }

            //下级是见习/初级/高级
            uidUserLevels = Lists.newArrayList(UserLevelEnum.TRAINEE.getValue(), UserLevelEnum.MEMBER.getValue(),
                    UserLevelEnum.SENIOR.getValue());

        } else {
            //下级是见习/初级/高级/超级
            uidUserLevels = Lists.newArrayList(UserLevelEnum.TRAINEE.getValue(), UserLevelEnum.MEMBER.getValue(),
                    UserLevelEnum.SENIOR.getValue(), UserLevelEnum.SUPER.getValue());

        }

        //校验下级用户等级
        if (!activityCommonUtilService.isUserLevelIn(uid, uidUserLevels)) {
            log.info("下级不是高级/超级,uid:{},pid:{},idempotent:{}", uid, pid, msg.getIdempotent());
            return ActivityCheckResultDTO.builder().satisfy(false).errorMsg("下级等级不符合").build();
        }
        //校验下级是否 外部未签约/外部已签约
        if (!activityCommonUtilService.isStaffFromTypeIn(uid,
                Lists.newArrayList(StaffFromTypeEnum.OUTER_NON_SIGN, StaffFromTypeEnum.OUTER_SIGN))) {
            log.info("下级不是外部未签约/外部已签约,uid:{},pid:{},idempotent:{}", uid, pid, msg.getIdempotent());
            return ActivityCheckResultDTO.builder().satisfy(false).errorMsg("下级不是外部未签约/外部已签约").build();
        }

        //校验pid
        ActivityCheckResultDTO activityCheckResultDTO = doPidAccessConditionCheck(pid, uid, msg.getIdempotent());
        if (activityCheckResultDTO != null) {
            return activityCheckResultDTO;
        }

        return ActivityCheckResultDTO.builder().satisfy(true).build();
    }

    /**
     * 查询标签
     * @param uid
     * @param becomeTraineeEvent
     * @param becomeTraineeTime
     * @return
     */
    private IrisQueryUsersInTagsResponse getUserTags(String uid, boolean becomeTraineeEvent, Long becomeTraineeTime) {
        /**
         * 成为见习事件后，延迟查询标签，以免刚成为见习的时候标没有打上
         */
        Long time = new Date().getTime() - becomeTraineeTime;
        if (becomeTraineeEvent && activityConfig.isDelayQueryUserTag()
                && time < 1000 * activityConfig.getDelayQueryUserTagSeconds()) {
            try {
                Thread.sleep(1000 * activityConfig.getDelayQueryUserTagSeconds());
            } catch (InterruptedException e) {
                throw new TRetailErrorException("延迟查询用户标签信息失败");
            }
        }

        IrisQueryUsersInTagsResponse userTags;
        if (becomeTraineeEvent) {
            //成为见习事件查询标签信息之前出现返回为空的bug（已修复），此处保留兜底逻辑
            userTags = activityCommonUtilService.waitAndRetry(() -> activityCommonUtilService.queryUserTags(uid),
                    t -> t == null || CollectionUtil.isEmpty(t.getTagIdList()), "查询用户标签信息失败");
        } else {
            userTags = activityCommonUtilService.queryUserTags(uid);
        }
        return userTags;
    }

    /**
     * 查询成为见习时间
     * @param uid
     * @param becomeTraineeEvent
     * @return
     */
    private Long queryTraineeTime(String uid, boolean becomeTraineeEvent) {
        Long becomeTraineeTime;
        if (becomeTraineeEvent) {
            becomeTraineeTime = activityCommonUtilService.waitAndRetry(() -> userCoreDependency.queryTraineeTime(uid),
                    Objects::isNull, "收到升级事件，用户成为见习会员的时间为空");
        } else {
            becomeTraineeTime = activityCommonService.queryTraineeTime(uid);
        }
        return becomeTraineeTime;
    }

    /**
     * 校验上级
     * @param pid
     * @param uid
     * @param idempotent
     * @return
     */
    public ActivityCheckResultDTO doPidAccessConditionCheck(String pid, String uid, String idempotent) {
        //上级用户等级范围定义，上级是初级/高级/超级
        List<String> pidUserLevels = Lists.newArrayList(UserLevelEnum.TRAINEE.getValue(), UserLevelEnum.MEMBER.getValue(),
                        UserLevelEnum.SENIOR.getValue(), UserLevelEnum.SUPER.getValue());
        if (pid == null) {
            log.info("pid为空,uid:{},pid:{},idempotent:{}", uid, null, idempotent);
            return ActivityCheckResultDTO.builder().satisfy(false).errorMsg("上级id为空").build();
        }
        //校验上级用户等级
        if (!activityCommonUtilService.isUserLevelIn(pid, pidUserLevels)) {
            log.info("上级不是高级/超级,uid:{},pid:{},idempotent:{}", uid, pid, idempotent);
            return ActivityCheckResultDTO.builder().satisfy(false).errorMsg("上级等级不符合").build();
        }
        //校验上级用户类型
        if (!activityCommonUtilService.isStaffFromTypeIn(pid,
                Lists.newArrayList(StaffFromTypeEnum.OUTER_NON_SIGN, StaffFromTypeEnum.OUTER_SIGN))) {
            log.info("上级不是外部未签约/外部已签约,uid:{},pid:{},idempotent:{}", uid, pid, idempotent);
            return ActivityCheckResultDTO.builder().satisfy(false).errorMsg("上级不是外部未签约/外部已签约").build();
        }

        return null;
    }

    /**
     * 获取严格口径开始执行的时间
     *
     * @return
     */
    private LocalDate getStrictStandardStartDate(String uid) {
        try {
            return LocalDate.parse(activityConfig.getStrictStandardStartTimeByUid(uid));

        } catch (Exception e) {
            log.error("getStrictStandardStartDate error", e);
        }
        return LocalDate.of(2023, 5, 1);
    }
}